/*
 * Copyright 2002-2004 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ 

package org.springframework.transaction.interceptor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

/**
 * Simple implementation of TransactionAttributeSource that
 * allows attributes to be matched by registered name.
 *
 * @author Juergen Hoeller
 * @see #isMatch
 * @since 21.08.2003
 */
public class NameMatchTransactionAttributeSource implements TransactionAttributeSource {

  protected final Log logger = LogFactory.getLog(getClass());

  private Map nameMap = new HashMap();

  /**
   * Set a name/attribute map, consisting of method names
   * (e.g. "myMethod") and TransactionAttribute instances.
   *
   * @see TransactionAttribute
   */
  public void setNameMap(Map nameMap) {
    this.nameMap = nameMap;
  }

  /**
   * Parses the given properties into a name/attribute map.
   * Expects method names as keys and String attributes definitions as values,
   * parsable into TransactionAttribute instances via TransactionAttributeEditor.
   *
   * @see #setNameMap
   * @see TransactionAttributeEditor
   */
  public void setProperties(Properties transactionAttributes) {
    TransactionAttributeEditor tae = new TransactionAttributeEditor();
    for (Iterator it = transactionAttributes.keySet()
            .iterator(); it.hasNext(); ) {
      String methodName = (String) it.next();
      String value = transactionAttributes.getProperty(methodName);
      tae.setAsText(value);
      TransactionAttribute attr = (TransactionAttribute) tae.getValue();
      addTransactionalMethod(methodName, attr);
    }
  }

  /**
   * Add an attribute for a transactional method.
   * Method names can end with "*" for matching multiple methods.
   *
   * @param methodName the name of the method
   * @param attr       attribute associated with the method
   */
  public void addTransactionalMethod(String methodName, TransactionAttribute attr) {
    logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]");
    this.nameMap.put(methodName, attr);
  }

  public TransactionAttribute getTransactionAttribute(Method method, Class targetClass) {
    String methodName = method.getName();
    TransactionAttribute attr = (TransactionAttribute) this.nameMap.get(methodName);
    if (attr != null) {
      return attr;
    } else {
      // look up most specific name match
      String bestNameMatch = null;
      for (Iterator it = this.nameMap.keySet()
              .iterator(); it.hasNext(); ) {
        String mappedName = (String) it.next();
        if (isMatch(methodName, mappedName) &&
                (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
          attr = (TransactionAttribute) this.nameMap.get(mappedName);
          bestNameMatch = mappedName;
        }
      }
      return attr;
    }
  }

  /**
   * Return if the given method name matches the mapped name.
   * The default implementation checks for "xxx*" and "*xxx" matches.
   * Can be overridden in subclasses.
   *
   * @param methodName the method name of the class
   * @param mappedName the name in the descriptor
   * @return if the names match
   */
  protected boolean isMatch(String methodName, String mappedName) {
    return (mappedName.endsWith("*") && methodName.startsWith(mappedName.substring(0, mappedName.length() - 1))) ||
            (mappedName.startsWith("*") && methodName.endsWith(mappedName.substring(1, mappedName.length())));
  }

}
